3D Transformations

Consider a point P, which, in cartesian coordinates is given by P(x,y,z). It can also be represented by a matrix in the form shown below:

$\begin{bmatrix} {x} \\{y} \\{z} \end{bmatrix}$

We can also represent a transformation A that transforms the point P into P'

$$ \begin{equation} \begin{bmatrix} {x'} \\{y'} \\{z'} \end{bmatrix} = \begin{bmatrix} {A1}&{A2}&{A3} \\{B1}&{B2}&{B3} \\{C1}&{C2}&{C3} \end{bmatrix} \begin{bmatrix} {x} \\{y} \\{z} \end{bmatrix} \end{equation} $$

where the matrix A, given by \begin{bmatrix} {A1}&{A2}&{A3} \\{B1}&{B2}&{B3} \\{C1}&{C2}&{C3} \end{bmatrix}

is known as the transformation matrix. The simplest transformation is the identity transformation, given by \begin{bmatrix} {1}&{0}&{0} \\{0}&{1}&{0} \\{0}&{0}&{1} \end{bmatrix}

The determinant of the matrix indicates the factor by which the volume of the cube changes after the transformation.

In the following notebook, we will see examples of transformation matrices.


In [9]:
import plotly
import plotly.offline as py
import plotly.graph_objs as go
py.init_notebook_mode(connected=True)
import numpy as np
from plotly.graph_objs import Surface
import json


Functions for Transformations

Rotation Transformations

These Transformations rotate a point about a given axis of rotation, and each of them have a characteristic matrix associated with them, parameterized by the angle of rotation $ \theta $ For example, the rotation about the X axis is given by:

\begin{bmatrix} {1}&{0}&{0} \\{0}&{\cos\theta}&{-\sin\theta} \\{0}&{\sin\theta}&{\cos\theta} \end{bmatrix}

Notice that when the angle of rotation is 0, it reduces to the identity transformation. The determinant of the matrix is also independent of the angle of rotation and is always equal to 1.


In [10]:
'''
All transformations in this code have the same structure. One line reshapes the input into the correct shape.
M is the transformation matrix,and the output point is reshaped into a a 2D array.
'''
def rotXaxis(initPoint,theta):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[1, 0, 0],
                   [0, np.cos(theta), -np.sin(theta)], 
                   [0, np.sin(theta), np.cos(theta)]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return [xpoint, ypoint, zpoint]




def rotYaxis(initPoint,theta):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[np.cos(theta), 0, np.sin(theta)],
                   [0, 1, 0],
                   [-np.sin(theta), 0, np.cos(theta)]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return [xpoint, ypoint, zpoint]


def rotZaxis(initPoint, theta):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[np.cos(theta), -np.sin(theta), 0],
                   [np.sin(theta), np.cos(theta), 0],
                   [0, 0 ,1]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)

Skew Transformations

A skew transformation, also called a shear transformation, converts a cube to a parallelepiped. An example of this is a deck of cards being pushed one way on the top, and the other at the bottom, causing the cards to slide. The angles too are parameterised by an angle of shear $\theta $. Since the matrix too has a determinant of 1, it can be shown that the volume of the cube doesn't change, i.e the volume of a parallelepiped is independent of the angle of inclination.


In [11]:
def skewXaxis(initPoint, angle):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[1, np.tan(angle), 0],
                   [0, 1, 0],
                   [0, 0 ,1]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)


def skewYaxis(initPoint, angle):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[1, 0, 0],
                   [np.tan(angle), 1, 0],
                   [0, 0 ,1]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)

def skewZaxis(initPoint, angle):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[1, 0, 0],
                   [0, 1, 0],
                   [0, np.tan(angle) ,1]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)

Scale Transformations

Scale transformations are matrices of the form

$$\begin{bmatrix} {\lambda_1}&{0}&{0} \\{0}&{\lambda_2}&{0} \\{0}&{0}&{\lambda_3} \end{bmatrix}$$

where $\lambda_1, \lambda_2,\lambda_3$ are the scaling factors on the X, Y and Z axes respectively. Hence, it is quite clear that the determinant, and hence, the volume changes by a factor of $\lambda_1\lambda_2\lambda_3$.


In [12]:
def scaleXaxis(initPoint,scale):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[scale, 0, 0],
                   [0, 1, 0],
                   [0, 0 ,1]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)

def scaleYaxis(initPoint,scale):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[1, 0, 0],
                   [0, scale, 0],
                   [0, 0 ,1]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)

def scaleZaxis(initPoint,scale):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[1, 0, 0],
                   [0, 1, 0],
                   [0, 0 ,scale]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)

def scaleAllAxes(initPoint, scale):
    initPoint = np.reshape(initPoint,(3,1))
    M = np.matrix([[scale, 0, 0],
                   [0, scale, 0],
                   [0, 0 ,scale]
                  ])
    newPoint = M*initPoint
    ptList = np.reshape(newPoint,(1,3)).tolist()[0]
    xpoint = ptList[0] 
    ypoint = ptList[1] 
    zpoint = ptList[2] 
    return (xpoint, ypoint, zpoint)

Master Function (One function to run them all, and in Plotly, bind them)


In [13]:
def master(transformation, initialParam, finalParam, xinit, yinit, zinit):
    t = np.linspace(initialParam, finalParam, 10)
    for i in range(10):
        xTrans = []
        yTrans = []
        zTrans = []
        
        for j in range(8):
            point = [xinit[j], yinit[j], zinit[j]]
            pointOut = transformation(point, t[i])
            if np.amax(pointOut) > 3:
                xTrans = xinit
                yTrans = yinit
                zTrans = zinit
                print("You've overshot the layout")
                return
            else:
                xTrans.append(pointOut[0])
                yTrans.append(pointOut[1])
                zTrans.append(pointOut[2])
        cubeTrans = go.Mesh3d(
        x = xTrans,
        y = yTrans,
        z = zTrans,
        colorscale = [['0', 'rgb(255,255,255)'],['0.5','rgb(0,133,202) '], ['1', 'rgb(0,62,116)']],
        intensity = [0, 0.142857142857143, 0.285714285714286, 0.428571428571429, 0.571428571428571, 0.714285714285714, 0.857142857142857, 1],
        i = [7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2],
        j = [3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3],
        k = [0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6],
        name='y',
        showscale=False
        )
        data.append(dict(cubeTrans, visible = False))
    return

Generates layout


In [14]:
def generateLayout(data):
    steps = []
    for i in range(len(data)):
        step = dict(
            method = 'restyle',
            args = ['visible', [False] * len(data)],
        )
        step['args'][1][i] = True 
        steps.append(step)

    sliders = [dict(
        active = 10,
        pad = {"t": 50},
        steps = steps
    )]
    layout = dict(
        sliders = sliders,
        width=600,height=600,
        title='3D Transformation',
        font = dict(family="Comic Sans MS"),
        scene = dict(
            xaxis = dict(range=[-5, 5], autorange=False, zeroline=True),
            yaxis = dict(range=[-5, 5], autorange=False, zeroline=True),
            zaxis = dict(range=[-5, 5], autorange=False, zeroline=True),
            aspectmode = 'cube',
            camera = dict(center=dict(x=0,y=0,z=0),eye=dict(x=1,y=-1,z=1))
                    ),
        plot_bgcolor='rgb(255, 255, 255)'
    )
    figure = dict(data=data, layout=layout)
    return figure

Initalizations


In [15]:
xTrans = [-1., -1., 1., 1., -1., -1., 1., 1.];
yTrans = [-1., 1., 1., -1., -1., 1., 1., -1.];
zTrans = [-1., -1., -1., -1., 1., 1., 1., 1.];

In [16]:
data = []
master(rotXaxis,0,(np.pi/2),xTrans, yTrans, zTrans)
master(rotYaxis,0,(np.pi/2),xTrans, yTrans, zTrans)
figure = generateLayout(data)
py.iplot(figure)



In [ ]:


In [ ]: